package main.java.com.chankudo.proxy;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyMain {
    public static void main(String[] args) throws Exception {
        /*
            静态代理示例
        */
        // PersonImpl personImpl = new PersonImpl();
        // StaticPersonProxy staticPersonProxy = new StaticPersonProxy(personImpl);
        // staticPersonProxy.eat();
        // staticPersonProxy.say("hello");

        /*
            jvm读取某一个类的字节文件到内存中,会生成一个对应类的class对象.
            当我们new一个类的对象的时候,实际上由该类对应的class对象产生目标对象.
            动态代理的思想:直接通过目标类的加载器在内存中生成一个代理类的class对象,
            而对应的增强操作则由 InvocationHander 来实现.
         */
        /*
            动态代理示例1
        */
        // // 生成了动态代理类的class对象
        // Class dynamicPersonProxyCls = Proxy.getProxyClass(PersonImpl.class.getClassLoader(), IPerson.class);
        // // 获取了这个代理类的构造函数,这个构造函数的参数是 InvocationHandler
        // Constructor dyProxyConsTructor = dynamicPersonProxyCls.getConstructor(InvocationHandler.class);
        // // 定义一个 InvocationHandler 的实例
        // //先new一个目标对象 再传给 invocationHandler
        // PersonImpl personImpl = new PersonImpl();
        // myinvocationHandler invocationHandler = new myinvocationHandler(personImpl);
        // //向动态代理类的class的构建传参,示例化一个动态代理类的对象
        // IPerson dynamicPersonProxy = (IPerson)dyProxyConsTructor.newInstance(invocationHandler);
        // dynamicPersonProxy.eat();
        // dynamicPersonProxy.say("hello");

        /*
            动态代理示例2 更常见的一种用法，
            直接使用 newProxyInstance 方法，屏蔽中间细节，直接获取代理类的实例。
         */
        //先new一个目标对象 再传给 invocationHandler
        PersonImpl personImpl = new PersonImpl();
        myinvocationHandler invocationHandler = new myinvocationHandler(personImpl);

        // Class[] interfaces = new Class[1];
        // interfaces[0]= IPerson.class;
        //Class[] interfaces = {IPerson.class};

        /**
         * 应该注意到 newProxyInstance 的classLoader 不一定要是目标对象的classLoader
         * 只要传入的 classLoder 可以 识别 .class文件，并加载jvm就行了。
         */

        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        //直接获取实例化的代理对象
        // IPerson dynamicPersonProxy = (IPerson)Proxy
        // .newProxyInstance(PersonImpl.class.getClassLoader(), interfaces, invocationHandler);
        IPerson dynamicPersonProxy = (IPerson)Proxy
        .newProxyInstance(classLoader, new Class[]{IPerson.class}, invocationHandler);
        // //向动态代理类的class的构建传参,示例化一个动态代理类的对象
        dynamicPersonProxy.eat();
        dynamicPersonProxy.say("hello");

        /*
        注意：一个invocationHandler可以传给多个代理实例
         */
        // IPerson dynamicPersonProxy2 = (IPerson)Proxy
        // .newProxyInstance(PersonImpl.class.getClassLoader(), PersonImpl.class.getInterfaces(), invocationHandler);
        IPerson dynamicPersonProxy2 = (IPerson)Proxy
        .newProxyInstance(classLoader, PersonImpl.class.getInterfaces(), invocationHandler);
        dynamicPersonProxy2.eat();
        dynamicPersonProxy2.say("hello");

        /*
            new了两个实例 dynamicPersonProxy 和 dynamicPersonProxy2
            对应生成的代理代理对象也是两个，但是因为 传入 invocationHandler 的 personImpl 是同一个
            所以doSomething里面的hashcode是相同的
         */
        if(dynamicPersonProxy2 == dynamicPersonProxy){
            System.out.println("其实只生成了一个代理对象");
        }else{
            System.out.println("其实生成了两个代理对象");
        }

        /*
          实际上可以仅凭一个接口的定义就可以生成一个代理类。
          具体接口里面的函数做了上面，可以在 invocationHandler 里面具体实现。
         */
        IPerson dynamicPersonProxy3 = (IPerson)Proxy
        .newProxyInstance(classLoader, PersonImpl.class.getInterfaces(), new InvocationHandler(){

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName = method.getName();
                if("say".equals(methodName)){
                    //Class[] params = method.getParameterTypes();
                    if(args.length > 0 && args[0].getClass() == String.class){
                        System.out.println("say:"+args[0].toString());
                    }else{
                        System.out.println("say lalala...");
                    }
                    return null;
                }else if ("eat".equals(methodName)){
                    System.out.println("eat");
                    return null;
                }
                return null;
            }
            
        });
        //这样照样可以执行接口中定义的方法
        dynamicPersonProxy3.eat();
        dynamicPersonProxy3.say("hello");

    }

}

class myinvocationHandler implements InvocationHandler{
        private Object target;

        myinvocationHandler(Object target){
            this.target = target;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("---- in invocationHandler invoke method ----");
            String mthName = method.getName();
            System.out.println("方法名为:" + mthName);

            
            String argStr = "";
            if(null != args){
                StringBuffer stringBuffer = new StringBuffer();
                for (Object arg : args) {
                    stringBuffer.append(arg.toString()).append(",");
                }
                argStr = stringBuffer.toString();
            }
            
            String clsName = proxy.getClass().getName();
            String plnStr = "代理对象为[%s],代理类为[%s],方法名为[%s],参数为[%s]".format(clsName,mthName,argStr);
            System.out.println(plnStr);

            //实际执行目标对象的方法
            Object obj =  method.invoke(target, args);

            System.out.println("---- invocationHandler invoke method return ----");
            return obj;
        }

    }